using System;
using System.Collections.Generic;
using System.Linq;
using Godot;

namespace Arcane.Source.Combat
{
    public abstract class Entity
    {
        public int maxHealth;
        private float _health;
        public float Health
        {
            get => _health;
            set
            {
                _health = Mathf.Clamp(value, 0, maxHealth);
                OnHealthChange?.Invoke(_health);
                if (_health == 0) OnDeath?.Invoke();
            }
        }
        public bool Dead => _health <= 0;

        private float _timeScale = 1.0f;
        public float TimeScale
        {
            get => _timeScale;
            set
            {
                _timeScale = Mathf.Clamp(value, 0, float.PositiveInfinity);
                OnTimeScaleChange?.Invoke(_timeScale);
            }
        }

        public readonly BaseUltraSpell ultra;
        public int MaxEnergy => ultra.MaxEnergy;
        public float Energy
        {
            get => ultra.Energy;
            set => ultra.Energy = value;
        }
        public bool UltraAuto
        {
            get => ultra.AutoEnabled;
            set
            {
                ultra.AutoEnabled = value;
            }
        }

        private IBehaviour _currentBehaviour = null;
        public IBehaviour CurrentBehaviour
        {
            get => _currentBehaviour;
            set
            {
                _currentBehaviour = value;
                OnBeginBehaviour?.Invoke(_currentBehaviour);
            }
        }
        private readonly Queue<IBehaviour> _behaviourQueue = [];

        private readonly Dictionary<StringName, ITrigger> _triggers;
        private readonly Dictionary<StringName, Modifier> _modifiers = [];
        private readonly Dictionary<StringName, Meta> _metaData = [];

        private readonly Queue<StringName> _invalidTriggers = [];
        private readonly Queue<(StringName, ITrigger)> _pendingTriggers = [];

        #region Events
        public event Action<float> OnHealthChange;
        public event Action OnDeath;
        public event Action<float> OnEnergyChange
        {
            add => ultra.OnEnergyChange += value;
            remove => ultra.OnEnergyChange -= value;
        }
        public event Action<float> OnTimeScaleChange;
        public event Action<DamageType, float, float> OnHurt;
        public event Action<DamageType, float, float> OnAttack;
        public event Action<float, float> OnCharge;
        public event Action<StringName, BaseStatusEffect> OnAddStatusEffect;
        public event Action<IBehaviour> OnBeginBehaviour;
        #endregion

        public Entity(int maxHealth, BaseUltraSpell ultraSpell, Dictionary<StringName, ITrigger> itemTriggers = null)
        {
            this.maxHealth = maxHealth;
            _health = maxHealth;
            ultra = ultraSpell;
            ultra.Bind(this);
            _triggers = itemTriggers ?? [];
        }

        public abstract Entity GetTarget(int target);

        public void Hurt(DamageType type, float damage)
        {
            var finalDamage = ApplyModifier(type.GetHurt(), damage);
            OnHurt?.Invoke(type, damage, finalDamage);
            Health -= finalDamage;
        }

        public void Attack(Entity target, DamageType type, float damage)
        {
            var finalDamage = ApplyModifier(type.GetAttack(), damage);
            OnAttack?.Invoke(type, damage, finalDamage);
            target.Hurt(type, finalDamage);
        }

        public void Charge(float value)
        {
            var final = ApplyModifier(ModifierType.Charge, value);
            OnCharge?.Invoke(value, final);
            Energy += final;
        }

        public virtual void Setup()
        {
            foreach (var trigger in _triggers.Values)
            {
                trigger.OnReady(this);
            }
        }

        public virtual void Tick()
        {
            TickTriggers();
            ultra.Tick();
            TickBehaviour();
        }

        private void TickTriggers()
        {
            foreach (var (name, trigger) in _triggers)
            {
                trigger.OnTick(this);
                if (!trigger.Valid) _invalidTriggers.Enqueue(name);
            }

            foreach (var name in _invalidTriggers)
            {
                _triggers[name].OnRemove(this);
                _triggers.Remove(name);
            }
            _invalidTriggers.Clear();

            while (_pendingTriggers.Count > 0)
            {
                var (name, trigger) = _pendingTriggers.Dequeue();
                if (_triggers.ContainsKey(name)) continue;
                trigger.OnReady(this);
                _triggers.Add(name, trigger);
                if (trigger is BaseStatusEffect eff && eff.Show)
                {
                    OnAddStatusEffect?.Invoke(name, eff);
                }
            }
        }

        private void TickBehaviour()
        {
            if (_currentBehaviour is IBehaviour behaviour)
            {
                if (behaviour.Tick >= behaviour.Duration)
                {
                    if (_behaviourQueue.TryDequeue(out var pending))
                    {
                        CurrentBehaviour = pending;
                    }
                    else
                    {
                        _currentBehaviour = null;
                        return;
                    }
                }
                else if (_behaviourQueue.TryPeek(out var peek) && peek.IsInstant && behaviour.Tick > behaviour.PreActTicks)
                {
                    CurrentBehaviour = _behaviourQueue.Dequeue();
                }
                behaviour.Tick += 1;
                if (behaviour.Tick == behaviour.PreActTicks)
                {
                    behaviour.Apply(this);
                }
            }
            else if (_behaviourQueue.TryDequeue(out var pending))
            {
                CurrentBehaviour = pending;
            }
        }

        public void QueueAct(IBehaviour behaviour) => _behaviourQueue.Enqueue(behaviour);

        public void AddModifier(StringName name, Modifier modifier) => _modifiers.Add(name, modifier);
        public void RemoveModifier(StringName name) => _modifiers.Remove(name);

        public void AddMeta(StringName name, Meta meta) => _metaData.Add(name, meta);
        public Meta GetMeta(StringName name) => _metaData.TryGetValue(name, out var meta) ? meta : null;
        public void RemoveMeta(StringName name) => _metaData.Remove(name);

        public void AddTrigger(StringName typeName, ITrigger trigger)
        {
            if (_triggers.TryGetValue(typeName, out var old))
            {
                old.OnStack(trigger);
            }
            else
            {
                _pendingTriggers.Enqueue((typeName, trigger));
            }
        }

        protected void AddInitialTrigger(StringName name, ITrigger trigger)
        {
            _triggers.TryAdd(name, trigger);
        }

        public ITrigger GetTrigger(StringName name) => _triggers.TryGetValue(name, out var trigger) ? trigger : null;

        public T GetTrigger<T>(StringName name) where T : class, ITrigger
            => _triggers.TryGetValue(name, out var trigger) ? (T)trigger : null;

        private float ApplyModifier(ModifierType type, float rawValue)
        {
            var iter = _modifiers.Values.Where(m => m.type == type);

            var mul = iter.Where(Modifier.IsMul).Aggregate(1.0f, Modifier.Accumulate);
            var add = iter.Where(Modifier.IsAdd).Aggregate(0.0f, Modifier.Accumulate);
            var finalMul = iter.Where(Modifier.IsFinalMul).Aggregate(1.0f, Modifier.Accumulate);
            var finalAdd = iter.Where(Modifier.IsFinalAdd).Aggregate(0.0f, Modifier.Accumulate);

            return Mathf.Clamp((rawValue * mul + add) * finalMul + finalAdd, 0, float.MaxValue);
        }

#if DEBUG
        public void PrintTriggerNames()
        {
            GD.Print("Triggers: ", string.Join('\n', _triggers.Keys));
        }
#endif
    }
}